package com.ting.log.db;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ting.common.constant.CommonConstant;
import com.ting.common.utils.*;
import com.ting.log.bo.LogPositionBo;
import com.ting.log.dto.LogOperateDTO;
import com.ting.log.dto.LogOperateSaveDTO;
import com.ting.log.dto.LogPositionDto;
import com.ting.log.handler.LogHandler;
import com.ting.task.factory.ExportTaskFactory;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;

@Service
@Slf4j
public class LogOperateServiceImpl extends ServiceImpl<LogOperateMapper, LogOperate> implements LogOperateService {

    @Autowired private HttpServletRequest request;
    @Autowired private LogHandler logHandler;

    @Override
    public Page<LogOperate> pageList(LogOperateDTO logOperateDTO) {
        Page<LogOperate> operatePage = new Page<>(logOperateDTO.getPageNo(),logOperateDTO.getPageSize());
        QueryWrapper<LogOperate> queryWrapper = new QueryWrapper<>();
        LambdaQueryWrapper<LogOperate> wrapper = queryWrapper.lambda()
                .like(TingObjectUtils.isNotEmpty(logOperateDTO.getUuid()), LogOperate::getUuid, logOperateDTO.getUuid())
                .like(TingObjectUtils.isNotEmpty(logOperateDTO.getIp()), LogOperate::getIp, logOperateDTO.getIp())
                .like(TingObjectUtils.isNotEmpty(logOperateDTO.getUa()), LogOperate::getUa, logOperateDTO.getUa())
                .eq(TingObjectUtils.isNotEmpty(logOperateDTO.getOperationName()), LogOperate::getOperationName, logOperateDTO.getOperationName())
                .like(TingObjectUtils.isNotEmpty(logOperateDTO.getSource()), LogOperate::getSource, logOperateDTO.getSource())
                .like(TingObjectUtils.isNotEmpty(logOperateDTO.getUserName()), LogOperate::getUserName, logOperateDTO.getUserName())
                .eq(TingObjectUtils.isNotEmpty(logOperateDTO.getUserId()), LogOperate::getUserId, logOperateDTO.getUserId());
        if (TingObjectUtils.isNotEmpty(logOperateDTO.getCreateTime()) && logOperateDTO.getCreateTime().size() == 2){
            wrapper.ge(LogOperate::getCreateTime, DateUtils.strToDate_yyyy_MM_dd_14(logOperateDTO.getCreateTime().get(0)))
                    .le(LogOperate::getCreateTime,DateUtils.strToDate_yyyy_MM_dd_14(logOperateDTO.getCreateTime().get(1)));
        }else {
            wrapper.ge(LogOperate::getCreateTime, DateUtils.calculateDateTime(LocalDateTime.now(),7, ChronoUnit.DAYS,false))
                    .le(LogOperate::getCreateTime,LocalDateTime.now());
        }
        wrapper.orderByDesc(LogOperate::getCreateTime);
        Page<LogOperate> page = page(operatePage, wrapper);
        if (TingObjectUtils.isEmpty(page)){
            return page;
        }
        //完整性校验
        page.getRecords().forEach(this::checkHmac);
        return page;
    }



    @Override
    public void saveLog(LogOperateSaveDTO operateSaveDTO) {
        this.buildRequestInfo(operateSaveDTO);
        save(this.buildLogOperateSaveDTO(operateSaveDTO));
    }

    @Override
    @SneakyThrows
    public void buildRequestInfo(LogOperateSaveDTO operateSaveDTO){
        operateSaveDTO.setRequestParam(JSON.toJSONString(RequestUtil.getRequestParam(request)));
        operateSaveDTO.setUa(IpUtils.getUa(request));
        operateSaveDTO.setIp(IpUtils.getIp(request));
        operateSaveDTO.setSource(IpUtils.getSource(request));
    }

    private LogOperate buildLogOperateSaveDTO(LogOperateSaveDTO operateSaveDTO){
        String uuid = UUIDUtils.get();
        LogOperate logOperate = operateSaveDTO.copyTo(LogOperate.class);
        if (TingObjectUtils.isNotEmpty(logOperate.getIp())){
            try {
                LogPositionDto logPositionDto = LogPositionDto.builder().ip(logOperate.getIp()).build();
                LogPositionBo logPositionBo = logHandler.ipToPosition(logPositionDto);
                if (TingObjectUtils.isNotEmpty(logPositionBo)){
                    logOperate.setPositionName(logPositionBo.getPosition());
                }
            }catch (Exception e){
                log.error("保存审计日志时，获取ip的地址失败",e);
            }
        }
        int indexOf = operateSaveDTO.getOperationName().indexOf("-");
        if (indexOf != -1 && operateSaveDTO.getOperationName().length() > indexOf + 1){
            logOperate.setOperationModule(operateSaveDTO.getOperationName().substring(0,indexOf));
            logOperate.setOperationName(operateSaveDTO.getOperationName().substring(indexOf+1));
        }
        logOperate.setUuid(uuid);
        logOperate.setCreateTime(DateUtils.localDateTimeToStr(LocalDateTime.now(),DateUtils.yyyy_MM_dd_14));
        String text = this.buildText(uuid,logOperate.getOperationModule(),logOperate.getOperationName(),logOperate.getCreateTime(),logOperate.getUserId(),logOperate.getSource(),
                logOperate.getIp(),logOperate.getUa(),logOperate.getPositionName(),logOperate.getResult(),logOperate.getRequestParam(),logOperate.getErrorMsg());
        logOperate.setCipherText(this.encrypt(text));
        logOperate.setHmac(this.hmac(text + AND + "cipherText=" + logOperate.getCipherText()));
        logOperate.setHmacFlag(true);
        return logOperate;
    }

    private static final String AND = "&";

    private String buildText(String uuid,String operationModule, String operationName, String createTime,Long userId,String source,String ip,String ua,String positionName,Boolean result,
                        String requestParam,String errorMsg){
        StringBuilder stringBuilder = new StringBuilder("uuid=").append(uuid);
        return stringBuilder.append(AND).append("operationModule=").append(operationModule)
                .append(AND).append("operationModule=").append(operationModule)
                .append(AND).append("operationName=").append(operationName)
                .append(AND).append("createTime=").append(createTime)
                .append(AND).append("userId=").append(userId)
                .append(AND).append("source=").append(source)
                .append(AND).append("ip=").append(ip)
                .append(AND).append("ua=").append(ua)
                .append(AND).append("positionName=").append(positionName)
                .append(AND).append("result=").append(result)
                .append(AND).append("requestParam=").append(requestParam)
                .append(AND).append("errorMsg=").append(errorMsg)
                .toString();
    }

    private String hmac(String text){
        return SHAUtils.SM3String(text.getBytes());
    }

    private String encrypt(String text){
        return SM4Util.encrypt(text);
    }

    private void checkHmac(LogOperate logOperate){
        String text = this.buildText(logOperate.getUuid(),logOperate.getOperationModule(),logOperate.getOperationName(),logOperate.getCreateTime(),
                logOperate.getUserId(),logOperate.getSource(),logOperate.getIp(),logOperate.getUa(),logOperate.getPositionName(),
                logOperate.getResult(),logOperate.getRequestParam(),logOperate.getErrorMsg());
        text = text + AND + "cipherText=" + logOperate.getCipherText();
        String hmac = hmac(text);
        if (!logOperate.getHmacFlag()){
            return;
        }
        if (logOperate.getHmac().equals(hmac)){
            return;
        }
        logOperate.setHmacFlag(false);
        log.error("完整性校验未通过,数据id：{}",logOperate.getId());
        this.updateHmacFlag(logOperate.getId());
    }

    private void updateHmacFlag(Long id){
        LogOperate logOperate = new LogOperate();
        logOperate.setId(id);
        logOperate.setHmacFlag(false);
        updateById(logOperate);
    }

    //TODO 差一个数据解密的方法

    @Override
    @Async(value = CommonConstant.ASYNC_TASK_EXECUTOR)
    public void saveLogAsync(LogOperateSaveDTO operateSaveDTO) {
        LogOperate logOperate = this.buildLogOperateSaveDTO(operateSaveDTO);
        save(logOperate);
    }

}
